﻿using IdentityServer3.AccessTokenValidation;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;

namespace Sidvall.Web.Host
{
    public static class Startup
    {
        #region Public Members

        #region ConfigureAuthentication

        public static void ConfigureAuthentication(IAppBuilder app, string authorityBaseAddress, string authorityUserInfoEndpoint, string authorityTokenEndpoint,
            string clientBaseAddress, string clientId, string clientSecret, string responseType, string scope,
            string invalidUserWebAddress, Sidvall.Security.TwoFactorProvider twoFactorProvider, Sidvall.Security.IServerContentProvider serverContentProvider,
            IAuthenticationTicketProvider authenticationTicketProvider)
        {
            ConfigureAuthentication(app, authorityBaseAddress, authorityUserInfoEndpoint, authorityTokenEndpoint,
                clientBaseAddress, clientId, clientSecret, responseType, scope,
                invalidUserWebAddress, twoFactorProvider, serverContentProvider, authenticationTicketProvider, null, null);
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
        public static void ConfigureAuthentication(IAppBuilder app, string authorityBaseAddress, string authorityUserInfoEndpoint, string authorityTokenEndpoint,
            string clientBaseAddress, string clientId, string clientSecret, string responseType, string scope,
            string invalidUserWebAddress, Sidvall.Security.TwoFactorProvider twoFactorProvider, Sidvall.Security.IServerContentProvider serverContentProvider,
            IAuthenticationTicketProvider authenticationTicketProvider, string issuerName, X509Certificate2 signingCertificate)
        {
            app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

            System.IdentityModel.Tokens.JwtSecurityTokenHandler.InboundClaimTypeMap = new System.Collections.Generic.Dictionary<string, string>();

            System.Web.Helpers.AntiForgeryConfig.UniqueClaimTypeIdentifier = Sidvall.Security.ClaimTypes.Subject;

            // for self contained tokens
            if (!string.IsNullOrWhiteSpace(issuerName))
            {
                app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
                {
                    Authority = authorityBaseAddress,
                    IssuerName = issuerName,
                    SigningCertificate = signingCertificate,
                });
            }
            else
            {
                app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
                {
                    Authority = authorityBaseAddress,
                });
            }

            // Enable the application to use a cookie to store information for the signed in user
            // and to use a cookie to temporarily store information about a user logging in with a third party login provider
            // Configure the sign in cookie
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = "ApplicationCookie",
            });
            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
            {
                ClientId = clientId,
                Authority = authorityBaseAddress,
                RedirectUri = clientBaseAddress,
                PostLogoutRedirectUri = clientBaseAddress,
                ResponseType = responseType,
                Scope = scope,
                SignInAsAuthenticationType = "ApplicationCookie",
                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    AuthorizationCodeReceived = async n =>
                    {
                        Sidvall.Security.TokenResponse tokenResponse;
                        Sidvall.Security.ServerContent serverContent;
                        Sidvall.Security.IdentityStatus userStatus;

                        serverContent = null;
                        userStatus = Sidvall.Security.IdentityStatus.Invalid;
                        try
                        {
                            // ServerContent
                            var authenticationHeader = new Sidvall.Net.AuthenticationHeader()
                            {
                                Scheme = n.ProtocolMessage.TokenType,
                                Parameter = n.ProtocolMessage.AccessToken,
                            };
                            serverContent = await Sidvall.Security.SecurityManager.GetServerContentAsync(n.AuthenticationTicket.Identity, authorityUserInfoEndpoint,
                                n.ProtocolMessage.AccessToken, false, true, true, serverContentProvider);
                            switch (serverContent.UserStatus)
                            {
                                case Sidvall.Security.IdentityStatus.RequiresVerification:
                                case Sidvall.Security.IdentityStatus.Valid:
                                    break;
                                default:
                                    throw new Sidvall.Security.UnauthorizedException();
                            }

                            // Tokens
                            if (!string.IsNullOrWhiteSpace(authorityTokenEndpoint))
                            {
                                tokenResponse = await Sidvall.Security.SecurityManager.GetTokenResponseFromCodeAsync(authorityTokenEndpoint, clientId, clientSecret, n.Code, n.RedirectUri,
                                    null, null, CancellationToken.None);
                            }
                            else
                            {
                                tokenResponse = null;
                            }

                            // Initialize claims
                            if (authenticationTicketProvider == null)
                                authenticationTicketProvider = new DefaultAuthenticationTicketProvider();
                            n.AuthenticationTicket = await authenticationTicketProvider.GetAuthenticationTicketAsync(n.AuthenticationTicket, serverContent, tokenResponse);
                        }
                        catch
                        {
                            if (!string.IsNullOrWhiteSpace(invalidUserWebAddress))
                            {
                                if (serverContent != null)
                                    userStatus = serverContent.UserStatus;
                                invalidUserWebAddress = invalidUserWebAddress.Replace("{0}", ((int)userStatus).ToString());
                                n.AuthenticationTicket = new AuthenticationTicket(new ClaimsIdentity(), new AuthenticationProperties()
                                {
                                    RedirectUri = invalidUserWebAddress,
                                });
                            }
                            else
                            {
                                n.AuthenticationTicket = new AuthenticationTicket(new ClaimsIdentity(), new AuthenticationProperties());
                            }
                        }
                    },

                    RedirectToIdentityProvider = n =>
                    {
                        switch (n.ProtocolMessage.RequestType)
                        {
                            case Microsoft.IdentityModel.Protocols.OpenIdConnectRequestType.AuthenticationRequest:
                                n.ProtocolMessage.AcrValues = Sidvall.Security.AuthenticationContext.GetAuthenticationContextReferences(twoFactorProvider);
                                if (n.OwinContext.Authentication.User.Identity.IsAuthenticated)
                                {
                                    n.ProtocolMessage.Prompt = "login";
                                }
                                break;
                            case Microsoft.IdentityModel.Protocols.OpenIdConnectRequestType.LogoutRequest:
                                var claim = n.OwinContext.Authentication.User.FindFirst(Sidvall.Security.ClaimTypes.IdentityToken);
                                if (claim == null)
                                    return Task.FromResult(false);
                                n.ProtocolMessage.IdTokenHint = claim.Value;
                                break;
                            default:
                                break;
                        }
                        return Task.FromResult(true);
                    },
                }
            });
        }

        #endregion

        #endregion
    }
}
